/* $FreeBSD: releng/11.2/sys/dev/usb/usb_handle_request.c 331722 2018-03-29 02:50:57Z eadler $ */
/*-
 * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#endif

/* function prototypes */

static uint8_t usb_handle_get_stall(struct usb_device *, uint8_t);
static usb_error_t     usb_handle_remote_wakeup(struct usb_xfer *, uint8_t);
static usb_error_t     usb_handle_request(struct usb_xfer *);
static usb_error_t     usb_handle_set_config(struct usb_xfer *, uint8_t);
static usb_error_t     usb_handle_set_stall(struct usb_xfer *, uint8_t,
                uint8_t);
static usb_error_t     usb_handle_iface_request(struct usb_xfer *, void **,
                uint16_t *, struct usb_device_request, uint16_t,
                uint8_t);

/*------------------------------------------------------------------------*
 *    usb_handle_request_callback
 *
 * This function is the USB callback for generic USB Device control
 * transfers.
 *------------------------------------------------------------------------*/
void
usb_handle_request_callback(struct usb_xfer *xfer, usb_error_t error)
{
    usb_error_t err;

    /* check the current transfer state */

    switch (USB_GET_STATE(xfer)) {
    case USB_ST_SETUP:
    case USB_ST_TRANSFERRED:

        /* handle the request */
        err = usb_handle_request(xfer);

        if (err) {

            if (err == USB_ERR_BAD_CONTEXT) {
                /* we need to re-setup the control transfer */
                usb_needs_explore(xfer->xroot->bus, 0);
                break;
            }
            goto tr_restart;
        }
        usbd_transfer_submit(xfer);
        break;

    default:
        /* check if a control transfer is active */
        if (xfer->flags_int.control_rem != 0xFFFF) {
            /* handle the request */
            (void)usb_handle_request(xfer);
        }
        if (xfer->error != USB_ERR_CANCELLED) {
            /* should not happen - try stalling */
            goto tr_restart;
        }
        break;
    }
    return;

tr_restart:
    /*
     * If a control transfer is active, stall it, and wait for the
     * next control transfer.
     */
    usbd_xfer_set_frame_len(xfer, 0, sizeof(struct usb_device_request));
    xfer->nframes = 1;
    xfer->flags.manual_status = 1;
    xfer->flags.force_short_xfer = 0;
    usbd_xfer_set_stall(xfer);    /* cancel previous transfer, if any */
    usbd_transfer_submit(xfer);
}

/*------------------------------------------------------------------------*
 *    usb_handle_set_config
 *
 * Returns:
 *    0: Success
 * Else: Failure
 *------------------------------------------------------------------------*/
static usb_error_t
usb_handle_set_config(struct usb_xfer *xfer, uint8_t conf_no) /*lint -e454 -e455*/
{
    struct usb_device *udev = xfer->xroot->udev;
    usb_error_t err = USB_ERR_NORMAL_COMPLETION;
    uint8_t do_unlock;

    /*
     * We need to protect against other threads doing probe and
     * attach:
     */
    USB_XFER_UNLOCK(xfer);

    /* Prevent re-enumeration */
    do_unlock = usbd_enum_lock(udev);

    if (conf_no == USB_UNCONFIG_NO) {
        conf_no = USB_UNCONFIG_INDEX;
    } else {
        /*
         * The relationship between config number and config index
         * is very simple in our case:
         */
        conf_no--;
    }

    if (usbd_set_config_index(udev, conf_no)) {
        DPRINTF("set config %d failed\n", conf_no);
        err = USB_ERR_STALLED;
        goto done;
    }
    if (usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY)) {
        DPRINTF("probe and attach failed\n");
        err = USB_ERR_STALLED;
        goto done;
    }
done:
    if (do_unlock)
        usbd_enum_unlock(udev);
    USB_XFER_LOCK(xfer);
    return (err);
}/*lint +e454 +e455*/

static usb_error_t
usb_check_alt_setting(struct usb_device *udev,
     struct usb_interface *iface, uint8_t alt_index)
{
    uint8_t do_unlock;
    usb_error_t err = USB_ERR_NORMAL_COMPLETION;

    /* Prevent re-enumeration */
    do_unlock = usbd_enum_lock(udev);

    if (alt_index >= usbd_get_no_alts(udev->cdesc, iface->idesc))
        err = USB_ERR_INVAL;

    if (do_unlock)
        usbd_enum_unlock(udev);

    return (err);
}

/*------------------------------------------------------------------------*
 *    usb_handle_iface_request
 *
 * Returns:
 *    0: Success
 * Else: Failure
 *------------------------------------------------------------------------*/
static usb_error_t
usb_handle_iface_request(struct usb_xfer *xfer,
    void **ppdata, uint16_t *plen,
    struct usb_device_request req, uint16_t off, uint8_t state) /*lint -e454 -e455*/
{
    struct usb_interface *iface;
    struct usb_interface *iface_parent;    /* parent interface */
    struct usb_device *udev = xfer->xroot->udev;
    int error;
    uint8_t iface_index;
    uint8_t temp_state;
    uint8_t do_unlock;

    if ((req.bmRequestType & 0x1F) == UT_INTERFACE) {
        iface_index = req.wIndex[0];    /* unicast */
    } else {
        iface_index = 0;    /* broadcast */
    }

    /*
     * We need to protect against other threads doing probe and
     * attach:
     */
    USB_XFER_UNLOCK(xfer);

    /* Prevent re-enumeration */
    do_unlock = usbd_enum_lock(udev);

    error = ENXIO;

tr_repeat:
    iface = usbd_get_iface(udev, iface_index);
    if ((iface == NULL) ||
        (iface->idesc == NULL)) {
        /* end of interfaces non-existing interface */
        goto tr_stalled;
    }
    /* set initial state */

    temp_state = state;

    /* forward request to interface, if any */

    if ((error != 0) &&
        (error != ENOTTY) &&
        (iface->subdev != NULL) &&
        device_is_attached(iface->subdev)) {
        error = USB_HANDLE_REQUEST(iface->subdev, /*lint !e611*/
            &req, ppdata, plen,
            off, &temp_state);
    }
    iface_parent = usbd_get_iface(udev, iface->parent_iface_index);

    if ((iface_parent == NULL) ||
        (iface_parent->idesc == NULL)) {
        /* non-existing interface */
        iface_parent = NULL;
    }
    /* forward request to parent interface, if any */

    if ((error != 0) &&
        (error != ENOTTY) &&
        (iface_parent != NULL) &&
        (iface_parent->subdev != NULL) &&
        ((req.bmRequestType & 0x1F) == UT_INTERFACE) &&
        (iface_parent->subdev != iface->subdev) &&
        device_is_attached(iface_parent->subdev)) {
        error = USB_HANDLE_REQUEST(iface_parent->subdev, /*lint !e611*/
            &req, ppdata, plen, off, &temp_state);
    }
    if (error == 0) {
        /* negativly adjust pointer and length */
        *ppdata = ((uint8_t *)(*ppdata)) - off;
        *plen += off;

        if ((state == USB_HR_NOT_COMPLETE) &&
            (temp_state == USB_HR_COMPLETE_OK))
            goto tr_short;
        else
            goto tr_valid;
    } else if (error == ENOTTY) {
        goto tr_stalled;
    }
    if ((req.bmRequestType & 0x1F) != UT_INTERFACE) {
        iface_index++;        /* iterate */
        goto tr_repeat;
    }
    if (state != USB_HR_NOT_COMPLETE) {
        /* we are complete */
        goto tr_valid;
    }
    switch (req.bmRequestType) {
    case UT_WRITE_INTERFACE:
        switch (req.bRequest) {
        case UR_SET_INTERFACE:
            /*
             * We assume that the endpoints are the same
             * accross the alternate settings.
             *
             * Reset the endpoints, because re-attaching
             * only a part of the device is not possible.
             */
            error = usb_check_alt_setting(udev,
                iface, req.wValue[0]);
            if (error) {
                DPRINTF("alt setting does not exist %s\n",
                    usbd_errstr(error));
                goto tr_stalled;
            }
            error = usb_reset_iface_endpoints(udev, iface_index);
            if (error) {
                DPRINTF("alt setting failed %s\n",
                    usbd_errstr(error));
                goto tr_stalled;
            }
            /* update the current alternate setting */
            iface->alt_index = req.wValue[0];
            break;

        default:
            goto tr_stalled;
        }
        break;

    case UT_READ_INTERFACE:
        switch (req.bRequest) {
        case UR_GET_INTERFACE:
            *ppdata = &iface->alt_index;
            *plen = 1;
            break;

        default:
            goto tr_stalled;
        }
        break;
    default:
        goto tr_stalled;
    }
tr_valid:
    if (do_unlock)
        usbd_enum_unlock(udev);
    USB_XFER_LOCK(xfer);
    return (USB_ERR_NORMAL_COMPLETION);

tr_short:
    if (do_unlock)
        usbd_enum_unlock(udev);
    USB_XFER_LOCK(xfer);
    return (USB_ERR_SHORT_XFER);

tr_stalled:
    if (do_unlock)
        usbd_enum_unlock(udev);
    USB_XFER_LOCK(xfer);
    return (USB_ERR_STALLED);
}/*lint +e454 +e455*/

/*------------------------------------------------------------------------*
 *    usb_handle_stall
 *
 * Returns:
 *    0: Success
 * Else: Failure
 *------------------------------------------------------------------------*/
static usb_error_t
usb_handle_set_stall(struct usb_xfer *xfer, uint8_t ep, uint8_t do_stall) /*lint -e454 -e455*/
{
    struct usb_device *udev = xfer->xroot->udev;
    usb_error_t err;

    USB_XFER_UNLOCK(xfer);
    err = usbd_set_endpoint_stall(udev,
        usbd_get_ep_by_addr(udev, ep), do_stall);
    USB_XFER_LOCK(xfer);
    return (err);
} /*lint +e454 +e455*/

/*------------------------------------------------------------------------*
 *    usb_handle_get_stall
 *
 * Returns:
 *    0: Success
 * Else: Failure
 *------------------------------------------------------------------------*/
static uint8_t
usb_handle_get_stall(struct usb_device *udev, uint8_t ea_val)
{
    struct usb_endpoint *ep;
    uint8_t halted;

    ep = usbd_get_ep_by_addr(udev, ea_val);
    if (ep == NULL) {
        /* nothing to do */
        return (0);
    }
    USB_BUS_LOCK(udev->bus);
    halted = ep->is_stalled;
    USB_BUS_UNLOCK(udev->bus);

    return (halted);
}

/*------------------------------------------------------------------------*
 *    usb_handle_remote_wakeup
 *
 * Returns:
 *    0: Success
 * Else: Failure
 *------------------------------------------------------------------------*/
static usb_error_t
usb_handle_remote_wakeup(struct usb_xfer *xfer, uint8_t is_on)
{
    struct usb_device *udev;
    struct usb_bus *bus;

    udev = xfer->xroot->udev;
    bus = udev->bus;

    USB_BUS_LOCK(bus);

    if (is_on) {
        udev->flags.remote_wakeup = 1;
    } else {
        udev->flags.remote_wakeup = 0;
    }

    USB_BUS_UNLOCK(bus);

#if USB_HAVE_POWERD
    /* In case we are out of sync, update the power state. */
    usb_bus_power_update(udev->bus);
#endif
    return (USB_ERR_NORMAL_COMPLETION);            /* success */
}

/*------------------------------------------------------------------------*
 *    usb_handle_request
 *
 * Internal state sequence:
 *
 * USB_HR_NOT_COMPLETE -> USB_HR_COMPLETE_OK v USB_HR_COMPLETE_ERR
 *
 * Returns:
 * 0: Ready to start hardware
 * Else: Stall current transfer, if any
 *------------------------------------------------------------------------*/
static usb_error_t
usb_handle_request(struct usb_xfer *xfer)
{
    struct usb_device_request req;
    struct usb_device *udev;
    const void *src_zcopy;        /* zero-copy source pointer */
    const void *src_mcopy;        /* non zero-copy source pointer */
    uint16_t off;            /* data offset */
    uint16_t rem;            /* data remainder */
    uint16_t max_len;        /* max fragment length */
    uint16_t wValue;
    uint8_t state;
    uint8_t is_complete = 1;
    usb_error_t err;
    union {
        uWord    wStatus;
        uint8_t    buf[2];
    }     temp;

    /*
     * Filter the USB transfer state into
     * something which we understand:
     */

    switch (USB_GET_STATE(xfer)) {
    case USB_ST_SETUP:
        state = USB_HR_NOT_COMPLETE;

        if (!xfer->flags_int.control_act) {
            /* nothing to do */
            goto tr_stalled;
        }
        break;
    case USB_ST_TRANSFERRED:
        if (!xfer->flags_int.control_act) {
            state = USB_HR_COMPLETE_OK;
        } else {
            state = USB_HR_NOT_COMPLETE;
        }
        break;
    default:
        state = USB_HR_COMPLETE_ERR;
        break;
    }

    /* reset frame stuff */

    usbd_xfer_set_frame_len(xfer, 0, 0);

    usbd_xfer_set_frame_offset(xfer, 0, 0);
    usbd_xfer_set_frame_offset(xfer, sizeof(req), 1);

    /* get the current request, if any */

    usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req));

    if (xfer->flags_int.control_rem == 0xFFFF) {
        /* first time - not initialised */
        rem = UGETW(req.wLength);
        off = 0;
    } else {
        /* not first time - initialised */
        rem = xfer->flags_int.control_rem;
        off = UGETW(req.wLength) - rem;
    }

    /* set some defaults */

    max_len = 0;
    src_zcopy = NULL;
    src_mcopy = NULL;
    udev = xfer->xroot->udev;

    /* get some request fields decoded */

    wValue = UGETW(req.wValue);

    DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x "
        "off=0x%x rem=0x%x, state=%d\n", req.bmRequestType,
        req.bRequest, wValue, UGETW(req.wIndex), off, rem, state);

    /* demultiplex the control request */

    switch (req.bmRequestType) { /*lint -e527*/
    case UT_READ_DEVICE:
        if (state != USB_HR_NOT_COMPLETE) {
            break;
        }
        switch (req.bRequest) {
        case UR_GET_DESCRIPTOR:
            goto tr_handle_get_descriptor;
        case UR_GET_CONFIG:
            goto tr_handle_get_config;
        case UR_GET_STATUS:
            goto tr_handle_get_status;
        default:
            goto tr_stalled;
        }
        break;

    case UT_WRITE_DEVICE:
        switch (req.bRequest) {
        case UR_SET_ADDRESS:
            goto tr_handle_set_address;
        case UR_SET_CONFIG:
            goto tr_handle_set_config;
        case UR_CLEAR_FEATURE:
            switch (wValue) {
            case UF_DEVICE_REMOTE_WAKEUP:
                goto tr_handle_clear_wakeup;
            default:
                goto tr_stalled;
            }
            break;
        case UR_SET_FEATURE:
            switch (wValue) {
            case UF_DEVICE_REMOTE_WAKEUP:
                goto tr_handle_set_wakeup;
            default:
                goto tr_stalled;
            }
            break;
        default:
            goto tr_stalled;
        }
        break;

    case UT_WRITE_ENDPOINT:
        switch (req.bRequest) {
        case UR_CLEAR_FEATURE:
            switch (wValue) {
            case UF_ENDPOINT_HALT:
                goto tr_handle_clear_halt;
            default:
                goto tr_stalled;
            }
            break;
        case UR_SET_FEATURE:
            switch (wValue) {
            case UF_ENDPOINT_HALT:
                goto tr_handle_set_halt;
            default:
                goto tr_stalled;
            }
            break;
        default:
            goto tr_stalled;
        }
        break;

    case UT_READ_ENDPOINT:
        switch (req.bRequest) {
        case UR_GET_STATUS:
            goto tr_handle_get_ep_status;
        default:
            goto tr_stalled;
        }
        break;
    default:
        /* we use "USB_ADD_BYTES" to de-const the src_zcopy */
        err = usb_handle_iface_request(xfer,
            USB_ADD_BYTES(&src_zcopy, 0), /*lint !e413*/
            &max_len, req, off, state);
        if (err == 0) {
            is_complete = 0;
            goto tr_valid;
        } else if (err == USB_ERR_SHORT_XFER) {
            goto tr_valid;
        }
        /*
         * Reset zero-copy pointer and max length
         * variable in case they were unintentionally
         * set:
         */
        src_zcopy = NULL;
        max_len = 0;

        /*
         * Check if we have a vendor specific
         * descriptor:
         */
        goto tr_handle_get_descriptor;
    } /*lint +e527*/
    goto tr_valid;

tr_handle_get_descriptor:
    err = (usb_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len);
    if (err)
        goto tr_stalled;
    if (src_zcopy == NULL)
        goto tr_stalled;
    goto tr_valid;

tr_handle_get_config:
    temp.buf[0] = udev->curr_config_no;
    src_mcopy = temp.buf;
    max_len = 1;
    goto tr_valid;

tr_handle_get_status:

    wValue = 0;

    USB_BUS_LOCK(udev->bus);
    if (udev->flags.remote_wakeup) {
        wValue |= UDS_REMOTE_WAKEUP;
    }
    if (udev->flags.self_powered) {
        wValue |= UDS_SELF_POWERED;
    }
    USB_BUS_UNLOCK(udev->bus);

    USETW(temp.wStatus, wValue);
    src_mcopy = temp.wStatus;
    max_len = sizeof(temp.wStatus);
    goto tr_valid;

tr_handle_set_address:
    if (state == USB_HR_NOT_COMPLETE) {
        if (wValue >= 0x80) {
            /* invalid value */
            goto tr_stalled;
        } else if (udev->curr_config_no != 0) {
            /* we are configured ! */
            goto tr_stalled;
        }
    } else if (state != USB_HR_NOT_COMPLETE) {
        udev->address = (wValue & 0x7F);
        goto tr_bad_context;
    }
    goto tr_valid;

tr_handle_set_config:
    if (state == USB_HR_NOT_COMPLETE) {
        if (usb_handle_set_config(xfer, req.wValue[0])) {
            goto tr_stalled;
        }
    }
    goto tr_valid;

tr_handle_clear_halt:
    if (state == USB_HR_NOT_COMPLETE) {
        if (usb_handle_set_stall(xfer, req.wIndex[0], 0)) {
            goto tr_stalled;
        }
    }
    goto tr_valid;

tr_handle_clear_wakeup:
    if (state == USB_HR_NOT_COMPLETE) {
        if (usb_handle_remote_wakeup(xfer, 0)) {
            goto tr_stalled;
        }
    }
    goto tr_valid;

tr_handle_set_halt:
    if (state == USB_HR_NOT_COMPLETE) {
        if (usb_handle_set_stall(xfer, req.wIndex[0], 1)) {
            goto tr_stalled;
        }
    }
    goto tr_valid;

tr_handle_set_wakeup:
    if (state == USB_HR_NOT_COMPLETE) {
        if (usb_handle_remote_wakeup(xfer, 1)) {
            goto tr_stalled;
        }
    }
    goto tr_valid;

tr_handle_get_ep_status:
    if (state == USB_HR_NOT_COMPLETE) {
        temp.wStatus[0] =
            usb_handle_get_stall(udev, req.wIndex[0]);
        temp.wStatus[1] = 0;
        src_mcopy = temp.wStatus;
        max_len = sizeof(temp.wStatus);
    }
    goto tr_valid;

tr_valid:
    if (state != USB_HR_NOT_COMPLETE) {
        goto tr_stalled;
    }
    /* subtract offset from length */

    max_len -= off;

    /* Compute the real maximum data length */

    if (max_len > xfer->max_data_length) {
        max_len = usbd_xfer_max_len(xfer);
    }
    if (max_len > rem) {
        max_len = rem;
    }
    /*
     * If the remainder is greater than the maximum data length,
     * we need to truncate the value for the sake of the
     * comparison below:
     */
    if (rem > xfer->max_data_length) {
        rem = usbd_xfer_max_len(xfer);
    }
    if ((rem != max_len) && (is_complete != 0)) {
        /*
             * If we don't transfer the data we can transfer, then
             * the transfer is short !
             */
        xfer->flags.force_short_xfer = 1;
        xfer->nframes = 2;
    } else {
        /*
         * Default case
         */
        xfer->flags.force_short_xfer = 0;
        xfer->nframes = max_len ? 2 : 1;
    }
    if (max_len > 0) {
        if (src_mcopy) {
            src_mcopy = USB_ADD_BYTES(src_mcopy, off); /*lint !e413*/
            usbd_copy_in(xfer->frbuffers + 1, 0,
                src_mcopy, max_len);
            usbd_xfer_set_frame_len(xfer, 1, max_len);
        } else {
            usbd_xfer_set_frame_data(xfer, 1,
                USB_ADD_BYTES(src_zcopy, off), max_len); /*lint !e613 !e413*/
        }
    } else {
        /* the end is reached, send status */
        xfer->flags.manual_status = 0;
        usbd_xfer_set_frame_len(xfer, 1, 0);
    }
    DPRINTF("success\n");
    return (USB_ERR_NORMAL_COMPLETION);            /* success */

tr_stalled:
    DPRINTF("%s\n", (state != USB_HR_NOT_COMPLETE) ?
        "complete" : "stalled");
    return (USB_ERR_STALLED);

tr_bad_context:
    DPRINTF("bad context\n");
    return (USB_ERR_BAD_CONTEXT);
}
